package com.hy.${packageCode}.shared.dbaccess.repository;


import com.querydsl.core.dml.DeleteClause;
import com.querydsl.core.dml.UpdateClause;
import com.querydsl.core.types.EntityPath;
import com.querydsl.core.types.Expression;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.PathBuilder;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPADeleteClause;
import com.querydsl.jpa.impl.JPAUpdateClause;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Tuple;
import jakarta.persistence.TypedQuery;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.support.JpaEntityInformation;
import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
import org.springframework.data.querydsl.EntityPathResolver;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.querydsl.SimpleEntityPathResolver;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.lang.Nullable;

import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Stream;

@Slf4j
@NoRepositoryBean
public class DBAccessRepositoryImpl<T> extends SimpleJpaRepository<T, Long>
        implements QuerydslPredicateExecutor<T>, DBAccessRepository<T> {
    private final OutputQueryDSLSupport<T> dslSupport;
    private final EntityPath<T> path;
    private final CustomQuerydslJpaPredicateExecutor<T> querydslJpaPredicateExecutor;


    public DBAccessRepositoryImpl(JpaEntityInformation<T, Long> entityInformation,
                                  EntityManager entityManager) {
        this(entityInformation, entityManager, SimpleEntityPathResolver.INSTANCE);
    }

    public DBAccessRepositoryImpl(JpaEntityInformation<T, Long> entityInformation,
                                  EntityManager entityManager,
                                  EntityPathResolver resolver) {
        super(entityInformation, entityManager);
        this.path = resolver.createPath(entityInformation.getJavaType());
        dslSupport = new OutputQueryDSLSupport<>(entityInformation.getJavaType(), entityManager);
        this.querydslJpaPredicateExecutor = new CustomQuerydslJpaPredicateExecutor<>(entityInformation, entityManager, resolver, getRepositoryMethodMetadata());
    }


    @Override
    public DeleteClause<JPADeleteClause> delete() {
        return dslSupport.delete(path);
    }

    @Override
    public DeleteClause<JPADeleteClause> delete(EntityPath<?> path) {
        return dslSupport.delete(path);
    }

    @Override
    public UpdateClause<JPAUpdateClause> update() {
        return dslSupport.update(path);
    }

    @Override
    public UpdateClause<JPAUpdateClause> update(EntityPath<?> path) {
        return dslSupport.update(path);
    }

    @Override
    public PathBuilder<T> builder() {
        return dslSupport.getBuilder();
    }

    @Override
    public JPQLQuery<T> from() {
        return dslSupport.from(path);
    }

    @Override
    public JPQLQuery<T> from(EntityPath<T> entityPath) {
        return dslSupport.from(entityPath);
    }

    @Override
    public <U> JPQLQuery<U> select(Expression<U> expression) {
        return JPAExpressions.select(expression);
    }

    @Override
    public TypedQuery<Tuple> nativeQuery(String nativeQuerySql) {
        return (TypedQuery<Tuple>) dslSupport.getEntityManager().createNativeQuery(nativeQuerySql, Tuple.class);
    }


    @Override
    public Stream<T> stream(@Nullable Predicate predicate, @Nullable Sort sort) {
        if (sort == null) {
            return querydslJpaPredicateExecutor.createJPAQuery(predicate).select(path).stream();
        }
        return dslSupport.getQuerydsl()
                .applySorting(sort, querydslJpaPredicateExecutor.createJPAQuery(predicate).select(path)).stream();
    }

    @Override
    public Stream<T> stream(@Nullable Predicate predicate, OrderSpecifier<?>... sort) {
        return querydslJpaPredicateExecutor.createJPAQuery(predicate).select(path).orderBy(sort).stream();
    }


    public Optional<T> findOne(Predicate predicate) {
        return querydslJpaPredicateExecutor.findOne(predicate);
    }

    public List<T> findAll(Predicate predicate) {
        return querydslJpaPredicateExecutor.findAll(predicate);
    }

    public List<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
        return querydslJpaPredicateExecutor.findAll(predicate, orders);
    }

    public List<T> findAll(Predicate predicate, Sort sort) {
        return querydslJpaPredicateExecutor.findAll(predicate, sort);
    }

    public List<T> findAll(OrderSpecifier<?>... orders) {
        return querydslJpaPredicateExecutor.findAll(orders);
    }

    public Page<T> findAll(Predicate predicate, Pageable pageable) {
        return querydslJpaPredicateExecutor.findAll(predicate, pageable);
    }

    public <S extends T, R> R findBy(Predicate predicate, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
        return querydslJpaPredicateExecutor.findBy(predicate, queryFunction);
    }

    public long count(Predicate predicate) {
        return querydslJpaPredicateExecutor.count(predicate);
    }

    public boolean exists(Predicate predicate) {
        return querydslJpaPredicateExecutor.exists(predicate);
    }

}
